// Package router implements the Envoy JWT Authentication HTTP filter.
package jwtauthentication

import (
	"fmt"
	routepbv3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
	jwtpb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/jwt_authn/v3"
	"github.com/golang/protobuf/proto"
	"github.com/golang/protobuf/ptypes"
	"google.golang.org/protobuf/types/known/anypb"
	"tmlake.com/api-gateway/istio-agent/pkg/util/slice"
	"tmlake.com/api-gateway/istio-agent/xds/httpfilter"
)

func init() {
	httpfilter.Register(builder{})
}

type builder struct {
}

const allowMissing = "allowMissing"

type RequirementRule struct {
	PathMatch string
	Require   JwtRequirement
}

type JwtRequirement struct {
	//any
	providerNames []string
}

// The rules generated by istio are handled simply here
//origins-0 is optional (if token from origins-0 is provided, it must be valid, but also allows
//missing token.)
//rules:
//- match:
//	  prefix: /
//  requires:
//    requiresAny:
//      requirements:
//      - providerName: origins-0
//      - allowMissing: {}

/* require origins-0 or origins-1 or (if token from origins-0 is provided, it must be valid, but also allows
//missing token.)
   rules:
   - match:
       prefix: /
     requires:
       requiresAny:
         requirements:
         - providerName: origins-0
         - providerName: origins-1
         - requiresAll:
             requirements:
             - requiresAny:
                 requirements:
                 - providerName: origins-0
                 - allowMissing: {}
             - requiresAny:
                 requirements:
                 - providerName: origins-1
                 - allowMissing: {}
*/
type Config struct {
	httpfilter.FilterConfig
	JwtProviders map[string]*jwtpb.JwtProvider
	Rules        []RequirementRule
}

func (builder) TypeURLs() []string {
	return []string{
		"type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication",
	}
}

func parseMath(match *routepbv3.RouteMatch) string {
	if match == nil {
		return ""
	}

	var path string
	switch match.PathSpecifier.(type) {
	case *routepbv3.RouteMatch_Prefix:
		path = match.GetPrefix()
	default:
		httpfilter.HttpfilterLogger.Warningf("jwtauthentication: unsupport RouteMatch.PathSpecifier type")
	}

	return path
}

func parseRequires(jwtRequirement *jwtpb.JwtRequirement) []string {
	var providerNames []string
	if jwtRequirement == nil {
		return providerNames
	}

	switch jwtRequirement.RequiresType.(type) {
	case *jwtpb.JwtRequirement_RequiresAny:
		if jwtRequirement.GetRequiresAny() != nil {
			for _, requirement := range jwtRequirement.GetRequiresAny().GetRequirements() {
				names := parseRequires(requirement)
				for _, name := range names {
					providerNames = append(providerNames, name)
				}

			}
		}
	case *jwtpb.JwtRequirement_RequiresAll:
		if jwtRequirement.GetRequiresAll() != nil {
			for _, requirement := range jwtRequirement.GetRequiresAll().GetRequirements() {
				names := parseRequires(requirement)
				for _, name := range names {
					providerNames = append(providerNames, name)
				}

			}
		}
	case *jwtpb.JwtRequirement_AllowMissing:
		if jwtRequirement.GetAllowMissing() != nil {
			providerNames = append(providerNames, allowMissing)
		}
	case *jwtpb.JwtRequirement_ProviderName:
		providerNames = append(providerNames, jwtRequirement.GetProviderName())
	default:
		httpfilter.HttpfilterLogger.Warningf("jwtauthentication: unsupport JwtRequirement.RequiresType type")
	}

	return providerNames
}

// Parsing is the same for the base config and the override config.
func parseConfig(jwtAuthenticationCfg *jwtpb.JwtAuthentication) (httpfilter.FilterConfig, error) {
	var requirementRules []RequirementRule
	// All the validation logic described in A41.
	for _, rule := range jwtAuthenticationCfg.GetRules() {
		var requirementRule RequirementRule
		requirementRule.PathMatch = parseMath(rule.Match)
		switch rule.RequirementType.(type) {
		case *jwtpb.RequirementRule_Requires:
			providers := parseRequires(rule.GetRequires())
			httpfilter.HttpfilterLogger.Debugf("jwtauthentication: providers %+v", providers)
			requirementRule.Require = JwtRequirement{
				providerNames: providers,
			}

			requirementRule.Require.providerNames = slice.RemoveDuplicateElement(requirementRule.Require.providerNames)
			requirementRules = append(requirementRules, requirementRule)
			httpfilter.HttpfilterLogger.Infof("jwtauthentication: providers(duplicate removal) %+v", requirementRule.Require.providerNames)
		case *jwtpb.RequirementRule_RequirementName:
			httpfilter.HttpfilterLogger.Warningf("jwtauthentication: unsupport RequirementRule.RequirementName")
		default:
			httpfilter.HttpfilterLogger.Warningf("jwtauthentication: unsupport RequirementRule.RequirementType type")
		}
	}

	return Config{Rules: requirementRules,
		JwtProviders: jwtAuthenticationCfg.GetProviders()}, nil
}

func (builder) ParseFilterConfig(cfg proto.Message) (httpfilter.FilterConfig, error) {
	if cfg == nil {
		httpfilter.HttpfilterLogger.Errorf("jwtauthentication: nil configuration message provided")
		return nil, fmt.Errorf("jwtauthentication: nil configuration message provided")
	}
	any, ok := cfg.(*anypb.Any)
	if !ok {
		httpfilter.HttpfilterLogger.Errorf("jwtauthentication: error parsing config %v: unknown type %T", cfg, cfg)
		return nil, fmt.Errorf("jwtauthentication: error parsing config %v: unknown type %T", cfg, cfg)
	}
	msg := new(jwtpb.JwtAuthentication)
	if err := ptypes.UnmarshalAny(any, msg); err != nil {
		httpfilter.HttpfilterLogger.Errorf("jwtauthentication: error parsing config %v: %v", cfg, err)
		return nil, fmt.Errorf("jwtauthentication: error parsing config %v: %v", cfg, err)
	}
	return parseConfig(msg)
}

func (builder) ParseFilterConfigOverride(override proto.Message) (httpfilter.FilterConfig, error) {
	if override == nil {
		httpfilter.HttpfilterLogger.Errorf("jwtauthentication: nil configuration message provided")
		return nil, fmt.Errorf("jwtauthentication: nil configuration message provided")
	}
	_, ok := override.(*anypb.Any)
	if !ok {
		httpfilter.HttpfilterLogger.Errorf("jwtauthentication: error parsing override config %v: unknown type %T", override, override)
		return nil, fmt.Errorf("jwtauthentication: error parsing override config %v: unknown type %T", override, override)
	}

	return Config{}, fmt.Errorf("jwtauthentication: error unsupport")
}

func (builder) IsTerminal() bool {
	return false
}
